//#define DRAW_BONES 

using System;
using System.Collections.Generic;
using Sce.PlayStation.Core;
using Sce.PlayStation.Core.Graphics;


namespace Nyanimals
{
	public class CAnimatedContext : CBaseContext
	{
		/* NOTE: These are from CBaseContext
			private bool active;
			private int meshIndex;
			private int shaderIndex;
			private float radius;
			private Matrix4 worldMatrix;
		 */
		private int animationIndex;
		private CInterpolator interpolator;
		private CBlender blender;
		private List<Matrix4> transformedBones;
		private List<Matrix4> untransformedBones;
		private List<Matrix4> bindPoseInverse; // TODO: Move to CAnimation
		
#if DRAW_BONES
		// TODO: Can remove when skinning working with PSM
		private List<CStaticContext> renderBones;
#endif
		private tFrameLimit currentAnimation;
		private tFrameLimit nextAnimation;
		private bool blend;
		private bool playing;
		//private bool queued;
		private float playbackSpeed;
		
		public CAnimatedContext() : base()
		{
			animationIndex = -1;
			interpolator = new CInterpolator();
			blender = new CBlender();
			
			
			bindPoseInverse = new List<Matrix4>();
			currentAnimation = new tFrameLimit();
			nextAnimation = new tFrameLimit();
			blend = false;
			playing = false;
			//queued = false;
			playbackSpeed = 1.0f;
			
		}
        
        public bool Blending
        {
            get { return blend;}
        }

		public void TransformedBones (int boneIndex, Matrix4 mat)
		{
			transformedBones[boneIndex] = mat;
		}
		public void UntransformedBones (int boneIndex, Matrix4 mat)
		{
			untransformedBones[boneIndex] = mat;
		}
		public void BindPoseInverse(int boneIndex, Matrix4 mat)
		{
			transformedBones[boneIndex] = bindPoseInverse[boneIndex] * mat;
		}
        
        public tFrameLimit CurrentAnimation
        {
            get { return currentAnimation;}
        }

		public int AnimationIndex
		{
			get { return animationIndex;}
			set 
			{ 
				animationIndex = value;
				interpolator.AnimationIndex = value;
				
				CAnimation anim = CAnimationManager.Instance.GetAnimation(value);
				
				// insert all the matrices to draw
#if DRAW_BONES
				renderBones = new List<CStaticContext>(30);
#endif
				transformedBones = new List<Matrix4>(30);
				untransformedBones = new List<Matrix4>(30);
				for(int boneIndex = 0; boneIndex < anim.Bones.Count; ++boneIndex)
				{
					Matrix4 worldLoc = anim.Bones[boneIndex].KeyFrames[0].LocalMatrix;
					
#if DRAW_BONES
					int meshIndex = CMeshManager.Instance.LoadMesh("debug_sphere.SRM");
					
					CStaticContext sphereContext;
					CContextManager.Instance.AddStaticContext(meshIndex, worldLoc, out sphereContext);
					sphereContext.WireFrame = true;
					
					renderBones.Insert(boneIndex, sphereContext);
#endif
					transformedBones.Insert(boneIndex, worldLoc);
					untransformedBones.Insert(boneIndex, worldLoc);
				}
			}
		}
		
		public void SetCurrentAnimation(tFrameLimit anim, float fPlaybackSpeed = 1.0f)
		{
			// change to next animation
			currentAnimation = anim;
			playbackSpeed = fPlaybackSpeed;
			playing = true;
			blend = false;
			
			CAnimation animation;
			CAnimationManager.Instance.GetAnimation(animationIndex, out animation);
			
			interpolator.Time = animation.Bones[0].KeyFrames[anim.StartFrame].KeyTime;
		}
		public void SetNextAnimation(tFrameLimit anim, float fPlaybackSpeed = 1.0f, float fTransitionTime = 0.1f)
		{
			// TODO: start blend
			nextAnimation = anim;
			playbackSpeed = fPlaybackSpeed;
			playing = true; 
			blend = true;
			//Console.WriteLine("blend start");
			blender.Setup(ref interpolator, ref currentAnimation, ref nextAnimation, fTransitionTime);
			
			
		}
		 // check if a particular animation is playing
		public bool IsPlaying(string szAnimation) 
		{
			return currentAnimation.Animation == szAnimation;
		}
		public bool Playing 
		{
			get { return playing;}
		}
		public bool OnFrame(string szFrame)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			
			return OnFrame(anim.Attribute(szFrame));
		}
		public bool BeforeFrame(string szFrame)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			
			return BeforeFrame(anim.Attribute(szFrame));
		}
		public bool AfterFrame(string szFrame)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			
			return AfterFrame(anim.Attribute(szFrame));
		}
		public bool BetweenFrames(string szFrame1, string szFrame2)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			
			int frame1 = anim.Attribute(szFrame1);
			int frame2 = anim.Attribute(szFrame2);
			
			if(frame1 == -1 || frame2 == -1)
			{
				return false;
			}
			
			return BetweenFrames(frame1, frame2);
			
		}
		public bool OnFrame(int nFrame)
		{
			return interpolator.CurrentFrame == nFrame;
		}
		public bool BeforeFrame(int nFrame)
		{
			return interpolator.CurrentFrame > nFrame;
		}
		public bool AfterFrame(int nFrame)
		{
			return interpolator.CurrentFrame > nFrame;
		}
		public bool BetweenFrames(int nFrame1, int nFrame2)
		{
			return interpolator.CurrentFrame >= nFrame1 && interpolator.CurrentFrame <= nFrame2;
		}
		
		Matrix4 GetJointMatrix(string szJointName)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			
			for(int boneIndex = 0; boneIndex < anim.Bones.Count; ++boneIndex)
			{
				if(anim.Bones[boneIndex].Name == szJointName)
				{
					// TODID: return the INTERPOLATED matrix
					// return anim.Bones[boneIndex].LocalMatrix;
					return untransformedBones[boneIndex];
				}
			}
			
			// safe for multiplies
			return Matrix4.Identity;
		}
		
		// the MAIN function
		public void ApplySkinning(float fTime, Matrix4 pParentMat)
		{
			CAnimation anim;
			CAnimationManager.Instance.GetAnimation(animationIndex, out anim);
			fTime *= playbackSpeed;
			
			if(blend)
			{
				// blend a bitch
				blender.Dissolve(ref transformedBones, ref untransformedBones, fTime, ref pParentMat);
				blend = blender.StillBlending;
				
				// check if the bitch is finishes
				if(blend == false)
				{
					currentAnimation = nextAnimation;
					interpolator.Time = anim.Bones[0].KeyFrames[currentAnimation.StartFrame].KeyTime;
					
					//Console.WriteLine("blend stop");
				}
			}
			else
			{
				// just interpolate if playing
				if(playing)
				{
					interpolator.Time += fTime;
					playing = interpolator.Process(transformedBones, untransformedBones, currentAnimation, pParentMat);
				}
				
			}
			
#if DRAW_BONES
			// update the bone contexts
			Matrix4 scale = Matrix4.Scale(0.25f, 0.25f, 0.25f);
			for(int boneIndex = 0; boneIndex < renderBones.Count; ++boneIndex)
			{
				renderBones[boneIndex].WorldMatrix = WorldMatrix * untransformedBones[boneIndex] * scale;				
			}
#endif
			// TODO: Check if something is on the queue
			// TODO: Make the animation queue
		}
		
		public override void Render(GraphicsContext graphics) 
		{
			// TODID: load in skeletal mesh
			// TODID: create shader
			// TODID: bind uniforms/attributes in context manager
			// TODO: Render the animation
			
			// leave in not active
			if(Active == false)
			{
				return;
			}
            
            foreach(RenderAction act in RenderActions)
            {
                act();
            }
            RenderActions.Clear();
         			
			// get our shader program
			tShader shader;
			CShaderManager.Instance.GetShader(ShaderIndex, out shader);
			
			// get our mesh
			CSkeletalMesh mesh = (CSkeletalMesh)CMeshManager.Instance.GetMesh(MeshIndex);
			
			// get the texture(s)
			tTexture tex;
			CTextureManager.Instance.GetTexture(mesh.TextureList[0], out tex);
			
			// calculate the world view projection
			CCamera cam = CCamera.Instance;
			Matrix4 wvp =  cam.Projection * cam.View * WorldMatrix;
			
			
			// set uniforms
			int wvpUniform = CContextManager.Instance.GetStaticUniform("WorldViewProj");
			int bonesUniform = CContextManager.Instance.GetAnimatedUniform("gBones");
			shader.Shader.SetUniformValue(wvpUniform, ref wvp);
			
			// send Bones			
			Matrix4[] arr = transformedBones.ToArray();
			for(int boneIndex = 0; boneIndex < 30; ++boneIndex)// NOTE: Max bone ammount
			{
				Matrix4 mat;
                if(boneIndex >= transformedBones.Count)
                {
                    mat = Matrix4.Identity;
                }
                else
                {
                    mat = arr[boneIndex];
                }
				shader.Shader.SetUniformValue(bonesUniform + boneIndex, ref mat);
			}
			
			// draw
			graphics.SetShaderProgram(shader.Shader);
       		graphics.SetVertexBuffer(0, mesh.VertBuffer);
			graphics.SetTexture(0, tex.Texture);	
        	graphics.DrawArrays(DrawMode.Triangles, 0, mesh.NumIndices);
			
		}
		
		public override ContextTypes Type
		{
			get { return ContextTypes.Animated_Context;}
		}
		
	}
}

